package il.ac.bgu.cs.heuristics.integration;

import il.ac.bgu.cs.heuristics.graph.GraphBuildEssentials;
import il.ac.bgu.cs.heuristics.graph.State;
import il.ac.bgu.cs.heuristics.graph.Substitution;
import il.ac.bgu.cs.heuristics.graph.pddl.SafeCast;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.AbstractParameter;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.ITypedConstant;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Predicate;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javaff.data.strips.Proposition;
import javaff.planning.FFState;

/**
 * Utility methods to integrate other project objects to this.
 * 
 * @author Ronen Pinko
 * 
 */
final class IntegrationUtils {

	private IntegrationUtils() {
	} // no instantiation

	/**
	 * Converts an FFState from the PDDL4J project to a state (copies only true
	 * propositions).
	 */
	private static IState trueOnlyStateFrom(FFState ffState,
			GraphBuildEssentials graphEssentials) {
		final Set<Proposition> propositions = ffState.getPropositions();
		final State.Builder trueOnlyBuilder = new State.Builder();
		for (Proposition proposition : propositions) {
			final Predicate predicate = predicateFromProposition(proposition,
					graphEssentials);
			final Substitution.Builder trueSub = new Substitution.Builder(
					predicate, graphEssentials.getResolver());
			for (AbstractParameter boundVar : predicate.getParameters()) {
				trueSub.addConstant(SafeCast.toTypedConstant(boundVar));
			}
			trueOnlyBuilder.addSubstitution(trueSub.build());
		}
		return trueOnlyBuilder.build();
	}

	private static Predicate predicateFromProposition(Proposition proposition,
			GraphBuildEssentials graphEssentials) {
		final String predicateName = proposition.getPredicateName();
		final List<String> parameters = proposition.getStringParameters();
		final Predicate parsedPredicate = graphEssentials.getNameToPredicate()
				.get(predicateName);
		if (parsedPredicate == null) {
			throw new IllegalArgumentException(
					"proposition predicate doesn't match any of the predicates parsed");
		}
		final Predicate.Builder predicateBuilder = new Predicate.Builder();
		predicateBuilder.name(parsedPredicate.getName());
		for (String param : parameters) {
			final ITypedConstant typedConstant = graphEssentials
					.getConstantByName(param);
			if (typedConstant == null) {
				throw new IllegalArgumentException(
						"propostition param doesn't match any of the typed constants parsed");
			}
			predicateBuilder.addVariable(SafeCast
					.toBoundParameter(typedConstant));

		}
		return predicateBuilder.build();

	}

	static IState stateFrom(FFState ffState,
			GraphBuildEssentials graphEssentials) {
		final IState trueOnlyState = trueOnlyStateFrom(ffState, graphEssentials);
		final List<Predicate> trueOnlyPredicates = getPredicates(trueOnlyState);
		final Map<String, List<List<ITypedConstant>>> nameToPredicateAssignments = createPredicateToUsedSubstitutions(trueOnlyPredicates);
		final State.Builder stateBuilder = new State.Builder(trueOnlyState
				.getSubstitutions());
		for (Predicate predicate : graphEssentials.getPredicates()) {
			final List<List<ITypedConstant>> assignments = nameToPredicateAssignments
					.get(predicate.getName());
			for (List<ITypedConstant> permutation : graphEssentials
					.getConstantsPermutations(predicate.arity())) {
				if (!predicate.isTypesAgree(permutation))
					continue;
				boolean shouldCreateFalseSub = true;
				if (assignments != null) {
					for (List<ITypedConstant> assignment : assignments) {
						if (permutation.equals(assignment)) {
							shouldCreateFalseSub = false;
							break;
						}
					}
				}

				if (shouldCreateFalseSub) {
					stateBuilder.addSubstitution(createFalseSubstitution(
							predicate, permutation, graphEssentials));
				}
			}
			if (predicate.arity() == 0
					&& !trueOnlyPredicates.contains(predicate)) {
				stateBuilder.addSubstitution(createFalseSubstitution(predicate,
						Collections.<ITypedConstant> emptyList(),
						graphEssentials));
			}
		}
		return stateBuilder.build();
	}

	private static boolean shouldAddTrueSubstitution(Predicate predicate) {
		return (predicate.getName().equalsIgnoreCase("path") || predicate
				.getName().equalsIgnoreCase("link"));
	}

	/**
	 * Returns a mapping of predicate name to all of its already used
	 * substitutions
	 */
	private static Map<String, List<List<ITypedConstant>>> createPredicateToUsedSubstitutions(
			List<Predicate> predicates) {
		final Map<String, List<List<ITypedConstant>>> nameToPredicateAssignments = CollectionUtils
				.newHashMap();
		for (Predicate predicate : predicates) {
			final String predicateName = predicate.getName();
			List<List<ITypedConstant>> assignments = nameToPredicateAssignments
					.get(predicateName);
			if (assignments == null) {
				assignments = CollectionUtils.newArrayList();
			}
			final List<ITypedConstant> assignment = CollectionUtils
					.newArrayList();
			for (AbstractParameter param : predicate.getParameters()) {
				assignment.add(SafeCast.toTypedConstant(param));
			}
			assignments.add(assignment);
			nameToPredicateAssignments.put(predicateName, assignments);
		}
		return nameToPredicateAssignments;
	}

	private static List<Predicate> getPredicates(IState state) {
		final List<Predicate> predicates = CollectionUtils.newArrayList();
		final Set<ISubstitution> substitutions = state.getSubstitutions();
		for (ISubstitution sub : substitutions) {
			predicates.add(sub.getPredicate());
		}
		return predicates;
	}

	static ISubstitution createFalseSubstitution(Predicate predicate,
			List<ITypedConstant> constants, GraphBuildEssentials buildEssentials) {
		final Predicate invertedPredicate = createOperatorInverted(predicate);
		final Substitution.Builder builder = new Substitution.Builder(
				invertedPredicate, buildEssentials.getResolver());
		for (ITypedConstant constant : constants) {
			builder.addConstant(constant);
		}
		return builder.build();
	}

	static ISubstitution createTrueSubstitution(Predicate predicate,
			List<ITypedConstant> constants, GraphBuildEssentials buildEssentials) {
		final Substitution.Builder builder = new Substitution.Builder(
				predicate, buildEssentials.getResolver());
		for (ITypedConstant constant : constants) {
			builder.addConstant(constant);
		}
		return builder.build();
	}

	/**
	 * Creates a new deep copied predicate with an inverted operator
	 */
	public static Predicate createOperatorInverted(Predicate predicate) {
		final Predicate.Builder builder = new Predicate.Builder()
				.name(predicate.getName());
		for (AbstractParameter param : predicate.getParameters()) {
			builder.addVariable(param);
		}
		builder.operator(predicate.getOperator().inverse());
		return builder.build();
	}

}
